﻿using System;

namespace Centido.Core.Test
{
    using System.Data.Common;
    using System.Linq;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// Testing role permissions
    /// </summary>
    [TestClass]
    public class RolePermissionsTest
    {
        /// <summary>
        /// Domain name
        /// </summary>
        public const string DomainName = "FullAdminRights";

        /// <summary>
        /// Domain
        /// </summary>
        private Domain _domain;

        /// <summary>
        /// A role
        /// </summary>
        private Role _role;

        /// <summary>
        /// Table
        /// </summary>
        private Table _table;

        /// <summary>
        /// Ordinary user
        /// </summary>
        private User _user1;

        /// <summary>
        /// Ordinary user domain
        /// </summary>
        private Domain _user1Domain;

        /// <summary>
        /// Ordinary user password
        /// </summary>
        private string _user1Password;

        /// <summary>
        /// Ordinary user
        /// </summary>
        private User _user2;

        /// <summary>
        /// Test initialize
        /// </summary>
        [TestInitialize]
        public void TestInitialize()
        {
            _domain = Domain.Build(DomainConfiguration.Load(DomainName));
            _domain.HasRecycleBin = new Random(Environment.TickCount).Next() % 2 == 1;

            _domain.DeleteAll();

            _table = _domain.AddTable(StringProperty.RandomIdentifier());

            var user1Name = StringProperty.RandomIdentifier();
            _user1Password = StringProperty.RandomPassword();

            _user1 = _domain.AddUser(user1Name, _user1Password);

            _user1.CanLogon = true;

            _user2 = _domain.AddUser(StringProperty.RandomIdentifier(), StringProperty.RandomPassword());

            _role = _domain.AddRole(StringProperty.RandomIdentifier());

            _role.AddUser(_user1);
            _role.AddUser(_user2);

            //Connecting after adding users in the role!!!
            _user1Domain = _domain.Logon(user1Name, _user1Password);
        }

        /// <summary>
        /// Test cleanup
        /// </summary>
        [TestCleanup]
        public void TestCleanup()
        {
            _domain.DeleteAll();
        }


        /// <summary>
        /// Granting and revoking role permissions
        /// </summary>
        [TestMethod]
        public void GrantingAndRevokingRolePermissions()
        {
            Assert.AreEqual(_role.TablePermissions.Count(), 0);
            Assert.AreEqual(_user1.TableAccessRights.Count(), 0);
            Assert.AreEqual(_user2.TableAccessRights.Count(), 0);
            Assert.AreEqual(_table.Permissions.Count(), 0);
            Assert.AreEqual(_table.AccessRights.Count(), 0);

            //Granting select all permission
            var perm = _table.Grant(_role, DataAction.Select);

            Assert.IsNotNull(perm);
            Assert.IsTrue(
                perm.Table.Id == _table.Id && perm.Principal.Name == _role.Name && perm.DataAction == DataAction.Select
                && perm.AccessLevel == AccessLevel.All);

            Assert.AreEqual(_role.TablePermissions.Count(), 1);
            Assert.AreEqual(
                _role.TablePermissions.Count(
                    p =>
                    p.Principal.Name == _role.Name && p.Table.Id == _table.Id && p.DataAction == DataAction.Select
                    && p.AccessLevel == AccessLevel.All),
                1);
            Assert.AreEqual(_user1.TableAccessRights.Count(), 1);
            Assert.AreEqual(_user2.TableAccessRights.Count(), 1);

            Assert.AreEqual(_table.Permissions.Count(), 1);
            Assert.AreEqual(
                _table.Permissions.Count(
                    p =>
                    p.Principal.Name == _role.Name && p.Table.Id == _table.Id && p.DataAction == DataAction.Select
                    && p.AccessLevel == AccessLevel.All),
                1);
            Assert.AreEqual(_table.AccessRights.Count(), 2);

            //Revoking the permission
            _table.Revoke(perm);

            Assert.AreEqual(_role.TablePermissions.Count(), 0);
            Assert.AreEqual(_user1.TableAccessRights.Count(), 0);
            Assert.AreEqual(_user2.TableAccessRights.Count(), 0);
            Assert.AreEqual(_table.Permissions.Count(), 0);
            Assert.AreEqual(_table.AccessRights.Count(), 0);

            //Granting update unit records permission
            perm = _table.Grant(_role, DataAction.Update, AccessLevel.Own);

            Assert.AreEqual(_role.TablePermissions.Count(), 1);
            Assert.AreEqual(
                _role.TablePermissions.Count(
                    p =>
                    p.Principal.Name == _role.Name && p.Table.Id == _table.Id && p.DataAction == DataAction.Update
                    && p.AccessLevel == AccessLevel.Own),
                1);
            Assert.AreEqual(_user1.TableAccessRights.Count(), 1);
            Assert.AreEqual(_user2.TableAccessRights.Count(), 1);

            Assert.AreEqual(_table.Permissions.Count(), 1);
            Assert.AreEqual(
                _table.Permissions.Count(
                    p =>
                    p.Principal.Name == _role.Name && p.Table.Id == _table.Id && p.DataAction == DataAction.Update
                    && p.AccessLevel == AccessLevel.Own),
                1);
            Assert.AreEqual(_table.AccessRights.Count(), 2);

            //Revoking the permission
            _table.Revoke(perm);

            Assert.AreEqual(_role.TablePermissions.Count(), 0);
            Assert.AreEqual(_user1.TableAccessRights.Count(), 0);
            Assert.AreEqual(_user2.TableAccessRights.Count(), 0);
            Assert.AreEqual(_table.Permissions.Count(), 0);
            Assert.AreEqual(_table.AccessRights.Count(), 0);
        }

        /// <summary>
        /// Granting permission to a nonexistent role
        /// </summary>
        [TestMethod]
        public void GrantingPermissionToNonexistentRole()
        {
            _role.Delete();

            try
            {
                _table.Grant(_role, DataAction.Select);
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.PrincipalWasNotFound);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Selecting with no select permission
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(DbException), AllowDerivedTypes = true)]
        public void SelectingWithNoSelectPermission()
        {
            var table = _user1Domain.GetTable(_table.Id);

            // ReSharper disable UnusedVariable
            var count = table.RowCount;
            // ReSharper restore UnusedVariable
        }

        /// <summary>
        /// Selecting with select all permission
        /// </summary>
        [TestMethod]
        public void SelectingWithSelectAllPermission()
        {
            //Granting select all permission to the role
            _table.Grant(_role, DataAction.Select);

            Assert.AreEqual(_role.TablePermissions.Count(), 1);
            Assert.AreEqual(_user1.TableAccessRights.Count(), 1);

            _table.Grant(_role, DataAction.Insert);

            //Inserting a row
            var table = _user1Domain.GetTable(_table.Name);
            table.InsertRow();

            _user1Domain.GetTable(_table.Name).InsertRow();

            //Ensuring that the user can read all rows
            Assert.AreEqual(_user1Domain.GetTable(_table.Name).RowCount, 2);
        }

        /// <summary>
        /// Applying and revoking multiple principal permissions
        /// </summary>
        /// <remarks>
        ///   (2)
        ///    |
        ///   (1, 3)
        ///    |
        ///   (4)
        /// 
        ///   _role: 1, 2
        ///   anotherRole: 1
        ///   baseRole: anotherRole
        /// 
        ///   _user1: select own 
        ///   _role: select unit
        ///   anotherRole: select child units
        ///   baseRole: select child and parent units
        /// </remarks>
        [TestMethod]
        public void ApplyingAndRevokingMultiplePrincipalPermissions()
        {
            //Building unit structure
            _domain.AddUnit(StringProperty.RandomIdentifier()).AddUser(_user1);
            _domain.AddUnit(StringProperty.RandomIdentifier()).AddUser(_user2);
            _user2.Units.ElementAt(0).AddUnit(_user1.Units.ElementAt(0));

            var user3 = _domain.AddUser(StringProperty.RandomIdentifier(), StringProperty.RandomPassword());

            _user1.Units.ElementAt(0).AddUser(user3);

            var user4 = _domain.AddUser(StringProperty.RandomIdentifier(), StringProperty.RandomPassword());

            _domain.AddUnit(StringProperty.RandomIdentifier()).AddUser(user4);
            _user1.Units.ElementAt(0).AddUnit(user4.Units.ElementAt(0));

            //Setting user permissions
            var user1Perm = _table.Grant(_user1, DataAction.Select, AccessLevel.Own);

            Assert.AreEqual(_user1.TableOwnRowsAccessRights.Count(), 1);
            Assert.AreEqual(_user1.TableUnitAccessRights.Count(), 0);

            //Adding permissions to user role
            var rolePerm = _table.Grant(_role, DataAction.Select, AccessLevel.Unit);

            Assert.AreEqual(_user1.TableAccessRights.Count(), 2);

            //Adding permissions to another role
            var anotherRole = _domain.AddRole(StringProperty.RandomIdentifier());

            anotherRole.AddUser(_user1);

            var anotherRolePerm = _table.Grant(anotherRole, DataAction.Select, AccessLevel.ChildUnits);

            Assert.AreEqual(_user1.TableUnitAccessRights.Count(), 2);
            Assert.AreEqual(_user1.TableOwnRowsAccessRights.Count(), 1);

            //Adding permissions to base role
            var baseRole = _domain.AddRole(StringProperty.RandomIdentifier());

            baseRole.AddRole(anotherRole);

            var baseRolePerm = _table.Grant(baseRole, DataAction.Select, AccessLevel.ChildAndParentUnits);

            Assert.AreEqual(_user1.TableUnitAccessRights.Count(), 3);
            Assert.AreEqual(_user1.TableOwnRowsAccessRights.Count(), 1);

            //Revoking user permissions
            _table.Revoke(user1Perm);

            Assert.AreEqual(_user1.TableUnitAccessRights.Count(), 3);
            Assert.AreEqual(_user1.TableOwnRowsAccessRights.Count(), 0);

            //Revoking another base role permission
            _table.Revoke(baseRolePerm);

            Assert.AreEqual(_user1.TableUnitAccessRights.Count(), 2);
            Assert.AreEqual(_user1.TableOwnRowsAccessRights.Count(), 0);

            //Revoking user role permissions
            _table.Revoke(rolePerm);

            Assert.AreEqual(_user1.TableUnitAccessRights.Count(), 2);
            Assert.AreEqual(_user1.TableOwnRowsAccessRights.Count(), 0);

            //Revoking another role permission
            _table.Revoke(anotherRolePerm);

            Assert.AreEqual(_user1.TableUnitAccessRights.Count(), 0);
            Assert.AreEqual(_user1.TableOwnRowsAccessRights.Count(), 0);
        }


        /// <summary>
        ///   Bug fix: role permissions on multiple tables.
        /// </summary>
        [TestMethod]
        public void GrantingAccessToMultipleTables()
        {
            var role = _domain.AddRole("my_role2323");

            var table = _domain.AddTable("mines");
            var anotherTable = _domain.AddTable("mines2");

            table.Grant(role, DataAction.Insert);
            anotherTable.Grant(role, DataAction.Insert);

            role.AddUser(_domain.CurrentUser);

            Assert.IsTrue(table.AccessRights.Any(r => r.DataAction == DataAction.Insert
                && r.User.Name == _domain.CurrentUser.Name));
            Assert.IsTrue(anotherTable.AccessRights.Any(r => r.DataAction == DataAction.Insert
                && r.User.Name == _domain.CurrentUser.Name));
        }


        /// <summary>
        /// Forming a cycle of roles
        /// </summary>
        [TestMethod]
        public void FormingCycleOfRoles()
        {
            var anotherRole = _domain.AddRole(StringProperty.RandomIdentifier());
            var baseRole = _domain.AddRole(StringProperty.RandomIdentifier());

            anotherRole.AddUser(_user1);

            baseRole.AddRole(anotherRole);
            baseRole.AddRole(_role);

            //Granting permissions to base role
            _table.Grant(baseRole, DataAction.Select, AccessLevel.Own);

            Assert.AreEqual(_user1.TableAccessRights.Count(), 1);

            //Deleting one child role
            anotherRole.Delete();

            Assert.AreEqual(_user1.TableAccessRights.Count(), 1);
        }
    }
}